Dota2Lounge Betting

The point of this notebook is to analyze matches from Dota2lounge to see what, if any, betting strategies would result in profit. Dota2lounge is a form of Paramutuel Betting where Dota 2 cosmetic items are bet on the outcomes of professional Dota 2 matches. The betting is explained in more detail here.

As for the purposes of this exploration, it was mostly for learning python and personal curiosity. Feedback / areas for improvement are welcome.

5/22/2014: dota2lounge now uses a different method for calculating payouts and bets, based on the steam marketplace value for the items. Means a lot of this only applies to the previous method for betting. It was still fun, but it may not be as useful if anyone was going to bet based on it. My sample includes matches #14-3098 excluding games where there was no winner on dota2lounge.


In [1]:
from pyd2l.match import Match, InvalidMatchError
from pyd2l.methods import unflatten
from pyd2l.plot import get_pick, simulate, plot_sims, plot_odds_correlation, plot_winners_vs_number_bets
import matplotlib.pyplot as plt
import matplotlib.offsetbox as ob
from statistics import mean
import csv
import numpy as np
from itertools import accumulate, groupby
import random
import matplotlib as mpl
from pprint import pprint
from functools import partial

%matplotlib inline
mpl.rc('xtick', labelsize=12) 
mpl.rc('ytick', labelsize=12) 
mpl.rc('font',size=20)

In [2]:
with open('match_cache.csv') as cache:
    match_reader = csv.reader(cache, delimiter=',')
    matches = [unflatten(row) for row in match_reader]

How well does the crowd do at predicting the winner?

This plot groups the matches by the crowd odds of the favorite, and then for each group of matches, calculates the percentage of matches that were won by the favorite. Also on this plot is a 1-1 line, which the bars would overlathe crowd was perfect at predicting winner odds (in the long run).

For example, in matches where the favorite's odd were between 75 and 78, the favorite won 76.8% of the time. The full results are below the plot.


In [3]:
# Sort all matches by the odds of the favorite, 51-100 inclusive
matches_sorted_favorite_odds = sorted([match for match in matches 
                                       if match.odds[match.favorite()]>50],
                                       key=lambda match: match.odds[match.favorite()])

fidelity = 3 # This is how tightly we want to group the matches by odds.

matches_grouped_favorite_odds = groupby(matches_sorted_favorite_odds,
                                        key=lambda m: ((m.odds[m.favorite()])//fidelity)*fidelity)
grouped = []

for group, group_matches in matches_grouped_favorite_odds:
    group_matches_list = list(group_matches)
    num_group_matches = len(group_matches_list)
    num_winners = len([match for match in group_matches_list if match.winner == match.favorite()])
    win_ratio = num_winners/num_group_matches
    grouped.append((group,num_group_matches,win_ratio))

groups,num_group_matches, ratios = zip(*[(group,num_group_matches,win_ratio) 
                                         for group,num_group_matches,win_ratio in grouped])

plot_odds_correlation(groups, num_group_matches, ratios, fidelity)


51 - 54 : 0.560
54 - 57 : 0.535
57 - 60 : 0.544
60 - 63 : 0.604
63 - 66 : 0.685
66 - 69 : 0.736
69 - 72 : 0.721
72 - 75 : 0.683
75 - 78 : 0.768
78 - 81 : 0.838
81 - 84 : 0.897
84 - 87 : 0.945
87 - 90 : 0.916
90 - 93 : 1.000

Any trend with larger number of bettors?

There might be a slight trend towards 50/50 as the number of bettors gets larger, but I don't think it's really anything significant or worth looking into.


In [39]:
num_bets = [match.num_bettors for match in matches]
winner_odds = [match.odds[match.winner] for match in matches]

plot_winners_vs_number_bets(num_bets,winner_odds)


Effects of different betting patterns

The following plots show the effects of betting different ways. To incorporate the effect of dota2lounge rounding the rewards if you win, I run a large number of trials, simulating that random assignment for winning bets. For example, if you win on a payout of 1.9 rares, you are guaranteed 1 item, and have a 90% chance to receive a second one. I am rounding in all of my trials.

Each line represents a trial of running through every match available and 'picking' a winner based on the test method, then determining the winner and payout, incorporating random chance (as described above). For each strategy (denoted by legend), I am running 500 simulations, taking D2L's random chance element into account.


In [5]:
plot_sims([[match.match_id for match in matches] for _ in range(3)],
          [simulate(matches, pick_method = 'random'),
           simulate(matches, pick_method = 'favorite'),
           simulate(matches, pick_method = 'underdog')],
          ['red','blue','green'],
          ['Bet Randomly','Bet on Favorite','Bet on Underdog'],
          'Betting on Odds')


In the first plot, I'm looking at different methods for choosing a winner. The two most obvious betting strategies are to bet on the underdog or the favorite. Betting on the favorite offers the best chance of becoming net positive, but still doesn't break even over 2000+ matches simulated 500 times.


In [6]:
plot_sims([[match.match_id for match in matches] for _ in range(3)],
          [simulate(matches, rarity = 'Rares'),
           simulate(matches, rarity = 'Uncommons'),
           simulate(matches, rarity = 'Commons')],
          ['red', 'blue', 'green'],
          ['Bet Rares', 'Bet Uncommons', 'Bet Commons'],
          'Betting Different Item Rarities (Random)')


This plot shows the differences in rarity payouts. These payouts depend on the number of items bet in each rarity level, and as shown, there is not a large disrepancy. The payout is slightly higher for rares though. I chose to leave keys out because they don't have as many samples as the other rarities, they were introduced much later into the website than the other options.


In [7]:
plot_sims([[match.match_id for match in matches] for _ in range(4)],
          [simulate(matches, num_bet=1),
           simulate(matches, num_bet=2),
           simulate(matches, num_bet=3),
           simulate(matches, num_bet=4)],
          ['red', 'blue', 'green', 'black'],
          ['Bet 1 item','Bet 2 items','Bet 3 items','Bet 4 items'],
          'Betting Different # of Items (Random)')


This is the first finding that surprised me a bit. It seems that when you bet one item (randomly selecting your choice) you are far more likely to come out ahead in the long run. One upside of only betting one item is the possibility to only lose that one item. However, you are also limiting your potential losses as well. There could be something factoring into this regarding the rounding that's done on dota2lounge's side.


In [8]:
plot_sims([[match.match_id for match in matches] for _ in range(4)],
          [simulate(matches, pick_method = 'favorite', num_bet=1),
           simulate(matches, pick_method = 'favorite', num_bet=2),
           simulate(matches, pick_method = 'favorite', num_bet=3),
           simulate(matches, pick_method = 'favorite', num_bet=4)],
          ['red', 'blue', 'green', 'black'],
          ['Bet 1 item','Bet 2 items','Bet 3 items','Bet 4 items'],
          'Betting Different # of Items (Favorite)')


This is maybe the most interesting plot of any, showing that when betting randomly, betting only one item offers a much higher payout, and actually averaging net positive total items over the long run. I'm interested to see what theories are for this, because theoretically the payouts should be proportional to the number bet, but there might be some insurance against loss by only betting 1 item.


In [9]:
plot_sims([[match.match_id for match in matches] for _ in range(3)],
          [simulate(matches, pick_method = 'favorite', num_bet=1, rarity='Rares'),
           simulate(matches, pick_method = 'favorite', num_bet=1, rarity='Uncommons'),
           simulate(matches, pick_method = 'favorite', num_bet=1, rarity='Commons')],
          ['red', 'blue', 'green'],
          ['Bet 1 Rare','Bet 1 Uncommon','Bet 1 Common'],
          'Betting Different Rarities (1 on Favorite)')


This is the last iteration of thsi betting strategy, showing the betting 1 Rare on the favorite would provide quite a nice profit of items over the long run. Now that D2L has changed to a value based system, this strategy would no longer apply.


In [10]:
plot_sims([[match.match_id for match in matches if match.odds[match.favorite()] > 60],
           [match.match_id for match in matches if match.odds[match.favorite()] > 70],
           [match.match_id for match in matches if match.odds[match.favorite()] > 80]],
          [simulate([match for match in matches if match.odds[match.favorite()] > 60], pick_method = 'favorite'),
           simulate([match for match in matches if match.odds[match.favorite()] > 70], pick_method = 'favorite'),
           simulate([match for match in matches if match.odds[match.favorite()] > 80], pick_method = 'favorite')],
          ['red', 'blue', 'green'],
          ['Bet on Favorite if odds >60','Bet on Favorite if odds >70','Bet on Favorite if odds >80'],
          "Effect of Betting if Odds are High (Favorite)")


The last strategies that I could think to test would be betting on either the favorite or the underdog only if the odds were past a threshold. Think of this as either betting on a "sure thing" or betting on an extreme underdog for the huge payout it could result in. Betting on the favorite if their odds are above a certain threshold here does vary the potential winnings, but still results in a net loss.


In [11]:
NUM_SIMS = 500
RARITY = 'Rares'
NUM_BET = 4

plot_sims([[match.match_id for match in matches if match.odds[match.underdog()] < 40],
           [match.match_id for match in matches if match.odds[match.underdog()] < 30],
           [match.match_id for match in matches if match.odds[match.underdog()] < 20]],
          [simulate([match for match in matches if match.odds[match.underdog()] < 40], pick_method = 'underdog'),
           simulate([match for match in matches if match.odds[match.underdog()] < 30], pick_method = 'underdog'),
           simulate([match for match in matches if match.odds[match.underdog()] < 20], pick_method = 'underdog')],
          ['red',    # 40
           'blue',   # 30
           'green'],
          ['Bet on Underdog if odds <40','Bet on Underdog if odds <30','Bet on Underdog if odds <20'],
          "Effect of Betting if Odds are Low (Underdog)")


Likewise, betting only on extreme underdogs results in very sharp losses.

Lastly, This is a plot of each team from TI3's average odds vs. their win percentage over those same games. This is to see teams that are overrated or underrated. I am aware that this method is not rigorous at all, and most likely dead wrong, even though there is a slight corellation.


In [14]:
TI3_teams = ["Alliance",
"Dignitas",
"LGD.cn",
"Liquid",
"Na'Vi",
"VP",
"DK",
"Fnatic",
"LGD.int",
"mouz",
"Orange",
"TongFu",
"Zenith"]

fig, ax = plt.subplots(figsize=(12, 10))
ax.set_xlim([40,80])
ax.set_ylim([40,80])

plt.plot([0,100],[0,100],'k')

team_percentages=[]

for team in TI3_teams:
    team_matches = [match for match in matches if team in match.teams]
    winners = [match for match in team_matches if team == match.winner]
    odds = mean([match.odds[team] for match in team_matches])
    wins = 100*len(winners)/len(team_matches)
    target_img = plt.imread("Team_Images/{}.png".format(team))
    target_oi = ob.OffsetImage(target_img,zoom=0.15)
    target_ab = ob.AnnotationBbox(target_oi, (odds, wins))
    target_ab.patch.set_alpha(0)
    target_art = ax.add_artist(target_ab)
    ax.set_xlabel('Average Odds')
    ax.set_ylabel('Win %')

plt.show()


Conclusions

There aren't many, although if d2l were running the old betting scheme still, I would advise the 1 rare on the favorite as a potential winning long-term strategy. Mostly, this was done for fun, so if you see other potential betting strategies, let me know.